#include "server.h"

static struct monitor_ctl monitor_ctl;
static struct listener_ctl listener_ctl;
static struct worker_ctl *a_worker_ctl;

struct _cnfg_info cnfg_info;
struct _share_info share_info;

static int _cnfg(char *argv0)
{/*{{{*/
#define DEF_ACCESS_TIMEOUT     SECOND_OF_MINUTE(60) 
    char dirname[LONG_FILE_NM_SIZE+1];
    char buf[LONG_FILE_NM_SIZE+1];
    int res;
    CNFG cnfg;
    struct cnfg_property property;

    extract_dirname(argv0, dirname);

    /*----- cnfg -----*/
    sprintf(buf, "%s/server.cnfg", dirname);
    if((res=cnfg_load(buf, &cnfg)))
    {
        printf("%s: load server.cnfg fail, errcode:%d\n",
                __func__, res);
        if(res > 0)
            printf("%s: line %d error\n", __func__, res);
        return -1;
    }

    // errlog
#ifdef _MY_DEBUG
    CLR_BUF(cnfg_info._errlog_.location);
    init_errlog(argv0, NULL);
#else
    strcpy(cnfg_info._errlog_.location, "./_log");
    if((res=cnfg_get_property(cnfg, "ERRLOG", "LOCATION", 0, &property)))
    {
        if(res != RET_ERR_CNFG_PROPERTY_NOT_FOUND)
        {
            fprintf(stderr, "%s: get ERRLOG.LOCATION fail, res:%d\n",
                    __func__, res);
            return(-1);
        }
    }
    else
    {
        if(!BUF_IS_CLR(property.value))
            strcpy(cnfg_info._errlog_.location, property.value);
    }
    if((res=init_errlog(argv0, cnfg_info._errlog_.location)))
    {
        fprintf(stderr, "%s: init_errlog fail, res:%d\n", __func__, res);
        return(-1);
    }
#endif
    if((res=cnfg_get_property(cnfg, "ERRLOG", "TRACKING", 0, &property)))
    {
        if(res != RET_ERR_CNFG_PROPERTY_NOT_FOUND)
        {
            fprintf(stderr, "%s: get ERRLOG.TRACKING fail, res:%d\n",
                    __func__, res);
            return(-1);
        }
        cnfg_info._errlog_.tracking=IS_FALSE;
    }
    else
        cnfg_info._errlog_.tracking=IS_TRUE;

    // USER
    CLR_BUF(cnfg_info.user);
    res=cnfg_get_property(cnfg, NULL, "USER", 0, &property);
    if(res)
    {
        if(res != RET_ERR_CNFG_PROPERTY_NOT_FOUND)
        {
            fprintf(stderr, "%s: get USER fail, res:%d\n",
                    __func__, res);
            return(-1);
        }
    }
    else
    {
        if(!BUF_IS_CLR(property.value))
        {
            if(strlen(property.value) > NAME_SIZE)
            {
                fprintf(stderr, "%s: USER invalid\n", __func__);
                return(-1);
            }
            strcpy(cnfg_info.user, property.value);
        }
    }

    // SERVER_PORT
    if((res=cnfg_get_property(cnfg, "ACCESS", "SERVER_PORT", 0, &property)))
    {
        fprintf(stderr, "%s: get ACCESS.SERVER_PORT fail, err:%d\n",
                __func__, res);
        return(-1);
    }
    if(BUF_IS_CLR(property.value) || (res=atoi(property.value)) < 1024)
    {
        fprintf(stderr, "%s: ACCESS.SERVER_PORT invalid\n", __func__);
        return(-1);
    }
    cnfg_info._access_.server_port=res;

    // MONITOR_PORT
    if((res=cnfg_get_property(cnfg, "ACCESS", "MONITOR_PORT", 0, &property)))
    {
        fprintf(stderr, "%s: get ACCESS.MONITOR_PORT fail, err:%d\n",
                __func__, res);
        return(-1);
    }
    if(BUF_IS_CLR(property.value) || (res=atoi(property.value)) < 1024)
    {
        fprintf(stderr, "%s: ACCESS.MONITOR_PORT invalid\n", __func__);
        return -1;
    }
    cnfg_info._access_.monitor_port=res;

    if((res=cnfg_get_property(cnfg, "SESSION", "ACCESS_TIMEOUT",
                    0, &property)))
    {
        fprintf(stderr, "%s: get SESSION.ACCESS fail, res:%d\n",
                __func__, res);
        return(-1);
    }
    if(!BUF_IS_CLR(property.value) && (res=atoi(property.value)) > 0)
        cnfg_info._session_.access_timeout=(time_t)res;
    else
        cnfg_info._session_.access_timeout=DEF_ACCESS_TIMEOUT;

    cnfg_free(cnfg);

    return(0);
}/*}}}*/

extern void svc_init();
extern int session_init(int *hash_num);

static int _init()
{/*{{{*/
    int worker_idx;
    SOCKET sock;
    SOCKET sock_pair[2];
    struct worker_ctl *worker_ctl;
    int hash_num;
    int res;
    char buf[FILE_NM_SIZE+1];
    int i, j;

    sig_ignore(SIGPIPE);
    ep_init();
    ossl_init_mt();

    if(getuid() == 0)
    {
        struct passwd *user=getpwnam(cnfg_info.user);
        if(user)
        {
            if(setuid(user->pw_uid) == -1)
            {
                fprintf(stderr, "Change to user %s fail\n",
                        cnfg_info.user);
                return(-1);
            }
            fprintf(stderr, "Change to user %s\n", cnfg_info.user);
        }
        else
        {
            fprintf(stderr, "User %s not found, keep root\n",
                    cnfg_info.user);
        }
    }

    if((session_init(&hash_num)))
    {
        fprintf(stderr, "%s: init session fail\n", __func__);
        return(-1);
    }
    ALLOC_ARRAY_DOERR(share_info.hash_users, int, hash_num,
            share_info.hash_num,
            fprintf(stderr, "%s: alloc share_info hash_users fail\n",
                __func__);
            return(-1));
    for(i=0; i<hash_num; ++i)
        share_info.hash_users[i]=0;

    /*----- share_info -----*/
    svc_init();
    pthread_mutex_init(&share_info.worker_info_lock, NULL);
    for(i=0; i<WORKER_NUM; ++i)
    {
        struct _worker_info *winfo=&share_info.worker_info[i];
        for(j=0; j<EP_POLL_MAX_NUM; ++j)
        {
            struct _client_info *cinfo=&winfo->client_info[j];
            cinfo->ip=NULL;
            cinfo->port=0;
            cinfo->loop=0;
            cinfo->count=0;
        }
        winfo->client_num=0;
    }

    /*--- thread monitor ---*/
    ALLOC_MULTI_RETERR(monitor_ctl.msg_in, char, BUFFER_SIZE+1, -1);
    ALLOC_MULTI_RETERR(monitor_ctl.msg_out, char, SOCK_MSG_SIZE+1, -1);
    sprintf(buf, "%d", cnfg_info._access_.monitor_port);
    if((res=skt_listen(buf, 0, &monitor_ctl.skt)))
    {
        fprintf(stderr, "%s: open monitor port fail\n", __func__);
        return(-1);
    }
    errlog("%s: open monitor port(%d)",
            __func__, cnfg_info._access_.monitor_port);

    // init thread listener slots
    if((res=ep_create(&listener_ctl.ep)))
    {
        fprintf(stderr, "%s: listener(ep_create) fail, res:%d\n",
                __func__, res);
        return(-1);
    }
    if((sock=sock_listen(cnfg_info._access_.server_port)) ==
            INVALID_SOCKET)
    {
        fprintf(stderr, "%s: open server port fail\n", __func__);
        return(-1);
    }
    if((res=ep_slot_register(listener_ctl.ep, sock, IS_TRUE,
                    &listener_ctl.listener_slot)))
    {
        fprintf(stderr, "%s: listener(ep_slot_register) server port fail, "
                "err:%d\n",
                __func__, res);
        return(-1);
    }
    ALLOC_MULTI_DOERR(listener_ctl.worker_slot, EP_SLOT, WORKER_NUM,
            fprintf(stderr, "%s: alloc listener_ctl.worker_slot fail\n",
                __func__);
            return -1);

    /*--- thread worker ---*/
    ALLOC_MULTI_DOERR(a_worker_ctl, struct worker_ctl, WORKER_NUM,
            fprintf(stderr, "%s: alloc a_worker_ctl fail\n", __func__);
            return(-1));
    for(worker_idx=0; worker_idx<WORKER_NUM; ++worker_idx)
    {
        // open listener-worker channel
        if(sock_local_pair(sock_pair))
        {
            fprintf(stderr, "%s: open listener-worker socket pair fail\n",
                    __func__);
            return -1;
        }
        if((res=ep_slot_register(listener_ctl.ep, sock_pair[0], IS_FALSE,
                        &listener_ctl.worker_slot[worker_idx])))
        {
            fprintf(stderr,
                    "%s: listener(ep_register) worker(%d) fail, err:%d\n",
                    __func__, worker_idx, res);
            return -1;
        }
        ep_slot_set_wrap(listener_ctl.worker_slot[worker_idx]);
        worker_ctl=&a_worker_ctl[worker_idx];
        if((res=ep_create(&worker_ctl->ep)))
        {
            fprintf(stderr,
                    "%s: worker(%d)(ep_slot_register) listener fail, "
                    "err:%d\n",
                    __func__, worker_idx, res);
            return(-1);
        }
        if((res=ep_slot_register(worker_ctl->ep, sock_pair[1], IS_FALSE,
                        &worker_ctl->listener_slot)))
        {
            fprintf(stderr,
                    "%s: worker(%d)(ep_slot_register) listener fail, "
                    "err:%d\n",
                    __func__, worker_idx, res);
            return -1;
        }
        ep_slot_set_wrap(worker_ctl->listener_slot);

        // start
        worker_ctl->worker_idx=worker_idx;
        //ALLOC_MULTI_DOERR(worker_ctl->msg_in, char, SOCK_MSG_SIZE+1,
        //        errlog("%s: alloc worker(%d) msg_in fail";
        //            __func__, worker_idx);
        //        return(-1));
        ALLOC_MULTI_DOERR(worker_ctl->msg_out, char, SOCK_MSG_SIZE+1,
                fprintf(stderr, "%s: alloc worker(%d) msg_out fail\n",
                    __func__, worker_idx);
                return(-1));
        worker_ctl->last_call=0;
        ALLOC_MULTI_DOERR(worker_ctl->svc_count, unsigned int,
                share_info.svc_num,
                fprintf(stderr, "%s: alloc worker(%d) svc_count fail\n",
                    __func__, worker_idx);
                return(-1));
        for(i=0; i<share_info.svc_num; ++i)
            worker_ctl->svc_count[i]=0;
        ALLOC_MULTI_DOERR(worker_ctl->client_count, unsigned int,
                EP_POLL_MAX_NUM,
                fprintf(stderr, "%s: alloc worker(%d) client_count fail\n",
                    __func__, worker_idx);
                return(-1));
        for(i=0; i<EP_POLL_MAX_NUM; ++i)
            worker_ctl->client_count[i]=0;
        if(thrd_create((THRD_FUNC_T)thrd_worker, (void *)worker_ctl,
                    THRD_DETACH, &worker_ctl->thrd_id))
        {
            fprintf(stderr, "%s: worker(%d) thrd create fail\n",
                    __func__, worker_idx);
            return(-1);
        }
    }

    /*--- thread listener ---*/
    if(thrd_create((THRD_FUNC_T)thrd_listener, (void *)&listener_ctl,
                THRD_DETACH, &listener_ctl.thrd_id))
    {
        fprintf(stderr, "%s: listener thrd create fail\n", __func__);
        return(-1);
    }

    return(0);
}/*}}}*/

int main(int argc, char *argv[])
{
#ifndef _MY_DEBUG
    daemon(1, 1);
#endif
    if(_cnfg(argv[0]))
        return(-1);
    if(_init())
        return(-2);
    if(thrd_monitor(&monitor_ctl))
        return(-3);

    return(0);
}
